package sk.stuba.fiit.foo07.genex.export;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.sql.Connection;
import java.util.ArrayList;

import net.iharder.base64.Base64;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import sk.stuba.fiit.foo07.genex.beans.Answer;
import sk.stuba.fiit.foo07.genex.beans.Picture;
import sk.stuba.fiit.foo07.genex.beans.Question;
import sk.stuba.fiit.foo07.genex.beans.QuestionType;
import sk.stuba.fiit.foo07.genex.common.ResourceHelper;
import sk.stuba.fiit.foo07.genex.common.SettingsHelper;
import sk.stuba.fiit.foo07.genex.dao.AnswerDao;
import sk.stuba.fiit.foo07.genex.dao.AnswerDaoDerby;
import sk.stuba.fiit.foo07.genex.dao.PictureDao;
import sk.stuba.fiit.foo07.genex.dao.PictureDaoDerby;
import sk.stuba.fiit.foo07.genex.dao.QuestionDao;
import sk.stuba.fiit.foo07.genex.dao.QuestionDaoDerby;
import sk.stuba.fiit.foo07.genex.dao.QuestionTypeDao;
import sk.stuba.fiit.foo07.genex.dao.QuestionTypeDaoDerby;
import sk.stuba.fiit.foo07.genex.exceptions.ExportException;

import com.sun.org.apache.xerces.internal.dom.DocumentImpl;
import com.sun.org.apache.xml.internal.serialize.OutputFormat;
import com.sun.org.apache.xml.internal.serialize.XMLSerializer;

/**
 * 
 * @author sochotnicky
 * 
 */
public class ExportMoodle extends Export {

    /**
     * 
     * @author sochotnicky
     * 
     * private aggregate class for more efficient conversion of questions into
     * HTML
     */
    private class QuestionAnswers {
        public QuestionAnswers(Question q, ArrayList<Answer> ans) {
            // we need deep copy (blee)
            this.question = new Question();
            this.question.setCreated(q.getCreated());
            this.question.setDerivedFromID(q.getDerivedFromID());
            this.question.setDifficulty(q.getDifficulty());
            this.question.setLastUpdate(q.getLastUpdate());
            this.question.setQuestionID(q.getQuestionID());
            this.question.setQuestionTypeID(q.getQuestionTypeID());
            this.question.setText(q.getText());
            this.question.setUserID(q.getUserID());

            this.answers = new ArrayList<Answer>(ans.size());
            for (Answer a : ans) {
                Answer newAns = new Answer();
                newAns.setAnswerID(a.getAnswerID());
                newAns.setIsCorrect(a.getIsCorrect());
                newAns.setQuestionID(a.getQuestionID());
                newAns.setText(a.getText());
                this.answers.add(newAns);
            }
        }

        public Question question;
        public ArrayList<Answer> answers;
    }

    /**
     * copy of questions to be converted
     */
    private ArrayList<QuestionAnswers> questions;

    private Connection con;

    private BufferedWriter heveaInput;
    private BufferedReader heveaOutput;
    private BufferedReader heveaERR;
    private Process heveaProc;

    private static String nl = System.getProperty("line.separator");

    private StringBuffer heveaOutputData;

    public ExportMoodle() {
        super();
        setOutputFilename("export-moodle.xml");
    }

    @Override
    public void exportQuestions(ArrayList<Question> questions, Connection con)
            throws IOException, ExportException {
        try {
            this.con = con;

            convertToHTML(questions);

            Document xmldoc = new DocumentImpl();
            Node root = xmldoc.createElement("quiz");

            int num = 1;
            for (QuestionAnswers q : this.questions) {
                Element e = createQuestionXMLElement(q, xmldoc, num++);
                root.appendChild(e);
            }

            xmldoc.appendChild(root);
            OutputFormat of = new OutputFormat("XML", "UTF-8", true);
            of.setIndent(1);
            of.setIndenting(true);

            XMLSerializer serializer = new XMLSerializer(new FileOutputStream(
                    getOutputFilename()), of);
            // As a DOM Serializer
            serializer.asDOMSerializer();
            serializer.serialize(xmldoc.getDocumentElement());

        } catch (IOException e) {
            System.err.println("Error running hevea/converting to HTML:" + e);
            e.printStackTrace();
            throw e;
        }
    }

    @Override
    public void exportTest(Integer testId, Connection con) throws IOException,
            ExportException {

        QuestionDao qDao = new QuestionDaoDerby(con);
        //TODO 
        //pridane odchytavanie vynimky (palo)
        try {
            exportQuestions(qDao.getQuestionsByTestID(testId), con);
        } catch (Exception e) {
        }
    }

    @Override
    public void exportTests(ArrayList<Integer> testIds, Connection con)
            throws IOException, ExportException {
        for (int tid : testIds)
            exportTest(tid, con);
    }

    /**
     * Converts questions and answers using HeVeA into HTML
     * 
     * Questions are separated by '=BEGINQ=' and '=ENDQ='. Also answers are
     * separated by similar tags.This way we can parse hevea output and store
     * modified answers
     * 
     * @param originalQ
     *                questions to be converted
     * @throws IOException
     */
    private void convertToHTML(ArrayList<Question> originalQ)
            throws IOException {

        heveaOutputData = new StringBuffer();
        newHeveaProcess();
        questions = new ArrayList<QuestionAnswers>(originalQ.size());

        AnswerDao aDao = new AnswerDaoDerby(con);
        int dataSize = 0;
        for (int i = 0; i < originalQ.size(); i++) {
            Question q = originalQ.get(i);
            if (dataSize >= 10000) {
                readHeveaOutput();
                dataSize = 0;
                newHeveaProcess();
            }

            heveaInput.write("=BEGINQ=");
            heveaInput.write(nl);
            heveaInput.write("=BEGINTEXT=");
            heveaInput.write(nl);
            //heveaInput.write(q.getText().trim());
            heveaInput.write(q.getText().trim());
            dataSize += q.getText().length();
            heveaInput.write(nl);
            heveaInput.write("=ENDTEXT=");
            heveaInput.write(nl);
            //TODO 
            //pridane odchytavanie vynimky (palo)
            ArrayList<Answer> answers = null; //pozor mozna chyba
            try {
                answers = aDao.getAnswersByQuestionID(q.getQuestionID());
            } catch (Exception e) {
            }
            questions.add(new QuestionAnswers(q, answers));

            for (Answer a : answers) {
                String text = a.getText();
                heveaInput.write("=BEGINANS=");
                heveaInput.write(nl);
                heveaInput.write(text.trim());
                dataSize += text.length();
                heveaInput.write(nl);
                heveaInput.write("=ENDANS=");
                heveaInput.write(nl);

            }

            heveaInput.write("=ENDQ=");
            heveaInput.write(nl);

            heveaInput.flush();
        }

        readHeveaOutput();

        parseHeveaOutput();
    }

    /**
     * this function reads data from HeVeA output stream (stdout)
     * 
     * @throws IOException
     */
    private void readHeveaOutput() throws IOException {
        char c = 0;
        heveaInput.close();
        while (heveaERR.ready()) {
            System.err.print((char) heveaERR.read());
        }

        while (heveaOutput.ready()) {
            c = (char) heveaOutput.read();
            heveaOutputData.append(c);
            System.err.print(c);
        }
        heveaOutput.close();
        heveaERR.close();
    }

    /**
     * Creates new HeVeA process and loads default macros
     * 
     * @throws IOException
     */
    private void newHeveaProcess() throws IOException {
        try {
            heveaProc = Runtime.getRuntime().exec(
                    SettingsHelper.getSetting("hevea_path"));
            heveaInput = new BufferedWriter(new OutputStreamWriter(heveaProc
                    .getOutputStream(),"UTF-8"));

            heveaOutput = new BufferedReader(new InputStreamReader(heveaProc
                    .getInputStream()));

            heveaERR = new BufferedReader(new InputStreamReader(heveaProc
                    .getErrorStream()));

            ResourceHelper mh = new ResourceHelper();
            String macros = mh.getMacros();

            heveaInput.write(macros);
        } catch (IOException e) {
            System.err.println("HeVeA execution failed: " + e);
            throw e;
        }
    }

    /**
     * parses output of HeVeA into ArrayList<QuestionAnswers> questions
     *
     */
    private void parseHeveaOutput() throws IOException {
        try {
            BufferedReader dataSourceReader = new BufferedReader(
                    new StringReader(heveaOutputData.toString()));
            String line = null;
            int converted = 0;

            while ((line = dataSourceReader.readLine()) != null) {
                if ("=BEGINQ=".equals(line)) {
                    // this reads =BEGINTEXT= from output
                    String tmpLine = dataSourceReader.readLine();
                    while (!"=BEGINTEXT=".equals(tmpLine)) {
                        tmpLine = dataSourceReader.readLine();
                    }

                    StringBuffer text = new StringBuffer();

                    // reads question text 
                    while (!(tmpLine = dataSourceReader.readLine())
                            .contains("=ENDTEXT=")) {
                        text.append(tmpLine);
                        text.append(nl);
                    }
                    /* end of line is =ENDTEXT= */
                    if (!tmpLine.equals("=ENDTEXT=")) {
                        text.append(tmpLine.replace("=ENDTEXT=", ""));
                    }

                    QuestionAnswers qa = questions.get(converted);
                    String tmp = text.toString();
                    if (tmp.contains("DIV CLASS=\"lstlisting\"")) {
                        tmp = tmp
                                .replace(
                                        "DIV CLASS=\"lstlisting\"",
                                        "DIV STYLE=\"font-family:monospace;white-space:pre;margin-right:auto;margin-left:0pt;text-align:left\"");
                        tmp = tmp.replace("/B> <B>", "/B>&nbsp;<B>");
                        //tmp = tmp.replace("/DIV", "/PRE");
                    }
                    qa.question.setText(tmp);
                    text = null;
                    int aNum = 0;
                    // read answers
                    while (!(tmpLine = dataSourceReader.readLine())
                            .contains("=ENDQ=")) {

                        while (!"=BEGINANS=".equals(tmpLine)) {
                            tmpLine = dataSourceReader.readLine();
                        }
                        text = new StringBuffer();
                        while (!(tmpLine = dataSourceReader.readLine())
                                .contains("=ENDANS=")) {
                            text.append(tmpLine);
                            text.append(nl);
                        }
                        if (!tmpLine.equals("=ENDANS=")) {
                            text.append(tmpLine.replace("=ENDANS=", ""));
                        }
                        tmp = text.toString();
                        if (tmp.contains("DIV CLASS=\"lstlisting\"")) {
                            tmp = tmp
                                    .replace(
                                            "DIV CLASS=\"lstlisting\"",
                                            "DIV STYLE=\"font-family:monospace;white-space:pre;margin-right:auto;margin-left:0pt;text-align:left\"");
                            tmp = tmp.replace("/B> <B>", "/B>&nbsp;<B>");
                        }

                        qa.answers.get(aNum++).setText(tmp);
                        text = null;
                    }

                    converted++;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
            throw new IOException("Error parsing hevea output:" + e);
        }

    }

    /**
     * Creates XML element from QuestionAnswers variable (one Node in moodle
     * export)
     * 
     * @param qa
     *                QuestionAnswers to be converted
     * @param xmldoc
     *                helper Document for creating elements
     * @return created xml element
     */
    private Element createQuestionXMLElement(QuestionAnswers qa,
            Document xmldoc, int num) throws ExportException {
        PictureDao pDao = new PictureDaoDerby(con);
        // TODO nedokoncena konverzia vsetkych dat
        QuestionTypeDao qtDao = new QuestionTypeDaoDerby(con);
        QuestionType qt = null;

        try {
            qt = qtDao.getQuestionTypeByQuestionID(qa.question.getQuestionID());
        } catch (Exception e) {
        }
        String strQType = null;
        if ("single choice".equalsIgnoreCase(qt.getName())
                || "multi choice".equalsIgnoreCase(qt.getName())) {
            strQType = new String("multichoice");
        } else if ("true/false".equalsIgnoreCase(qt.getName())) {
            strQType = new String("truefalse");
        } else if ("fill in".equalsIgnoreCase(qt.getName())) {
            strQType = new String("shortanswer");
        } else if ("essay".equalsIgnoreCase(qt.getName())) {
            strQType = new String("essay");
        } else {
            throw new ExportException("Unknown question type " + qt.getName()
                    + " for moodle export");
        }
        Element qElement = xmldoc.createElementNS(null, "question");
        qElement.setAttribute("type", strQType);
        Element qNameElement = xmldoc.createElementNS(null, "name");
        Element textElement = xmldoc.createElementNS(null, "text");
        textElement.appendChild(xmldoc.createTextNode("Otázka č. " + num));
        qNameElement.appendChild(textElement);
        qElement.appendChild(qNameElement);

        Element qTextElement = xmldoc.createElementNS(null, "questiontext");
        textElement = xmldoc.createElementNS(null, "text");
        Node textNode = xmldoc.createTextNode(qa.question.getText());

        textElement.appendChild(textNode);
        qTextElement.appendChild(textElement);
        qElement.appendChild(qTextElement);

        int correct = 0;
        for (Answer a : qa.answers) {
            if (a.getIsCorrect()) {
                correct++;
            }
        }

        for (Answer a : qa.answers) {
            Element aElement = xmldoc.createElementNS(null, "answer");
            aElement.setAttribute("fraction", a.getIsCorrect() == true ? String
                    .valueOf(100 / correct) : "0");

            textElement = xmldoc.createElementNS(null, "text");
            textNode = xmldoc.createTextNode(a.getText());
            textElement.appendChild(textNode);
            aElement.appendChild(textElement);
            qElement.appendChild(aElement);
        }

        ArrayList<Picture> pics = null; //pozor mozna chyba
        //TODO 
        //pridane odchytavanie vynimky (palo)
        try {
            pics = pDao.getPicturesByQuestionID(qa.question.getQuestionID());
        } catch (Exception e) {
        }

        for (Picture pic : pics) {
            Element imageElement = xmldoc.createElementNS(null, "image");
            textNode = xmldoc.createTextNode("genex-import/" + pic.getName());
            imageElement.appendChild(textNode);
            qElement.appendChild(imageElement);

            Element imgDataElement = xmldoc.createElementNS(null,
                    "image_base64");
            imgDataElement.appendChild(xmldoc.createTextNode(Base64
                    .encodeBytes(pic.getContent())));

            qElement.appendChild(imgDataElement);
        }

        if ("multichoice".equalsIgnoreCase(strQType)) {
            Element singleElement = xmldoc.createElementNS(null, "single");
            if ("single choice".equalsIgnoreCase(qt.getName())) {
                textNode = xmldoc.createTextNode("true");
            } else {
                textNode = xmldoc.createTextNode("false");
            }
            singleElement.appendChild(textNode);
            qElement.appendChild(singleElement);
        }

        return qElement;
    }
}
